﻿//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	SqlContext.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Web.Caching;
using Smart.Common.Entities;

namespace Smart.Common
{
	/// <summary>
	/// Microsoft SQL Server Scope
	/// </summary>
	[DebuggerDisplay("SqlContext Name={Name}")]
	public class SqlEntityScope : EntityScope, ISqlEntityScope
	{
		/// <summary>
		/// SQL Connection string builder
		/// </summary>
		public SqlConnectionStringBuilder ConnectionStringBuilder { get; set; }

		/// <summary>
		/// SQL Tables
		/// </summary>
		public HashSet<string> Tables { get; set; }

		/// <summary>
		/// Microsoft SQL Server Scope
		/// </summary>
		public SqlEntityScope()
		{
			Tables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		}

		/// <summary>
		/// Returns opened connection
		/// </summary>
		public virtual SqlConnection GetConnection()
		{
			var connection = new SqlConnection(ConnectionStringBuilder.ToString());

			connection.Open();

			return connection;
		}

		/// <summary>
		/// Returns new command
		/// </summary>
		/// <returns></returns>
		public virtual SqlCommand GetCommand()
		{
			var connection = GetConnection();

			return connection.CreateCommand();
		}

		/// <summary>
		/// Parse settings and fills <paramref name="ConnectionStringBuilder"/> and <paramref name="FacadeMapping"/>
		/// </summary>
		/// <param name="scopeSettings">scope settings</param>
		public virtual void ParseSettings(string[] scopeSettings)
		{
			if (scopeSettings.Length < 4)
				throw new ConfigurationErrorsException("scopeSettings");

			var serverName = scopeSettings[1];
			var databaseName = scopeSettings[2];
			//var userName = scopeSettings[3];
			//var password = scopeSettings[4];

			if (H.IsAnyNullOrEmpty(serverName, databaseName))
				throw new NotSupportedException();

			var rnd = new Random();

			ConnectionStringBuilder = new SqlConnectionStringBuilder
										{
											Pooling = true,
											ApplicationName =
												string.Format(SqlConst.ConnectionApplicationName, Name, System.Threading.Thread.CurrentThread.ManagedThreadId.ToString("X").EnsureLength(4),
															  rnd.Next(0, 65536).ToString("X").EnsureLength(4)),
											DataSource = serverName,
											InitialCatalog = databaseName,
											IntegratedSecurity = true,
											ConnectTimeout = SqlConst.NormalConnectionTimeout
										};

			for (var I = 3; I < scopeSettings.Length; I++)
			{
				var tableSettingsString = scopeSettings[I];

				if (tableSettingsString.IsNullOrEmpty())
					continue;

				var tableSettings = tableSettingsString.Split('#').ToList();

				for (var i = 0; i < tableSettings.Count; i++)
				{
					var sv = tableSettings[i];

					if (!sv.IsNullOrEmpty())
						tableSettings[i] = sv.Trim();
				}

				if (tableSettings.Count < 5)
					throw new ConfigurationErrorsException("scopeSettings");

				if (tableSettings.Count < 14)
					tableSettings.AddRange(new string[14 - tableSettings.Count]);

				var fi = 0;

				var tableFacade = tableSettings[fi++];
				var tableType = tableSettings[fi++].SToE(SqlObjectType.Table);
				var tableName = tableSettings[fi++];
				var primaryKey = tableSettings[fi++];
				var titleKey = tableSettings[fi++];

				// FacadeName or TableName could be skipped
				if (tableFacade.IsNullOrEmpty()) tableFacade = tableName;
				if (tableName.IsNullOrEmpty()) tableName = tableFacade;

				// Title key can be NULL or EMPTY
				if (titleKey.IsNullOrEmpty()) titleKey = null;

				if (H.IsAnyNullOrEmpty(tableFacade, tableName, primaryKey))
					continue;

				var spAdd = tableSettings[fi++];
				var spUpdate = tableSettings[fi++];
				var spDelete = tableSettings[fi++];

				if (spAdd.IsNullOrEmpty()) spAdd = null;
				if (spUpdate.IsNullOrEmpty()) spUpdate = null;
				if (spDelete.IsNullOrEmpty()) spDelete = null;

				var resultCodeKey = tableSettings[fi++];
				var resultDescriptionKey = tableSettings[fi++];

				if (resultCodeKey.IsNullOrEmpty()) resultCodeKey = "ResultCode";
				if (resultDescriptionKey.IsNullOrEmpty()) resultDescriptionKey = "Description";

				var cacheUserDependent = (tableSettings[fi++] ?? string.Empty).ToLower().Replace("userdependent", "true").SToBool(false);
				var cacheAbsoluteTimeout = tableSettings[fi++].SToTS(TimeSpan.MinValue);
				var cacheSlidingExpiration = tableSettings[fi++].SToTS(TimeSpan.MinValue);
				var cachePriority = tableSettings[fi++].SToE(CacheItemPriority.BelowNormal);

				Tables.Add(tableName);

				FacadeMapping.Add(tableFacade,
								  new SqlFacadeMappingInfo(tableFacade, tableName, tableType, primaryKey, titleKey)
									{
										SPAdd = spAdd,
										SPUpdate = spUpdate,
										SPDelete = spDelete,
										ResultCodeKey = resultCodeKey,
										ResultDescriptionKey = resultDescriptionKey,
										CacheUserDependent = cacheUserDependent,
										CacheAbsoluteTimeout = cacheAbsoluteTimeout,
										CacheSlidingExpiration = cacheSlidingExpiration,
										CachePriority = cachePriority
									});
			}
		}

		public static string GetCacheKey(string scopeName, string serverName, string databaseName)
		{
			return
				scopeName.GetHashCodeX() + "-" +
				serverName.GetHashCodeX() +
				databaseName.GetHashCodeX();
		}

		/// <summary>
		/// Init scope with settings
		/// </summary>
		/// <param name="name">Name of Scope</param>
		/// <param name="settings">Settings, first parameter is scope type name</param>
		public override void InitScope(string name, params string[] settings)
		{
			if (name == null)
				throw new ArgumentNullException("name");

			if (settings == null)
				throw new ArgumentNullException("settings");

			if (settings.Length < 4)
				throw new ConfigurationErrorsException("scopeSettings");

			Name = name;

			ParseSettings(settings);

			var scopeKey = Name + "-" + GetCacheKey(Name, ConnectionStringBuilder.DataSource, ConnectionStringBuilder.InitialCatalog);

			Meta = MetaCtx.GetScope(scopeKey, tmp => LoadMetaScope());
		}

		/// <summary>
		/// Return sql scope settings header
		/// </summary>
		protected virtual string GetDefaultSettingsHeader(string scopeTypeName, string serverName, string databaseName)
		{
			return
				string.Format(scopeTypeName + "\r\n" + serverName + "\r\n" + databaseName);
		}

		/// <summary>
		/// Return default scope settings
		/// </summary>
		protected override string GetDefaultSettings(string scopeTypeName, string scopeName)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Load metadata for scope
		/// </summary>
		protected override MetaScope LoadMetaScope()
		{
			var conn = GetConnection();

			var db = new MetaSqlDatabase(conn.Database);

			db.LoadLists(conn, Tables, FacadeMapping);

			return db;
		}

		/// <summary>
		/// Return source
		/// </summary>
		public override object GetSource(MetaList list)
		{
			var sqlObject = (MetaSqlObject)list;

			var conn = GetConnection();

			var cmd = conn.CreateCommand();

			if (sqlObject.ObjectType == SqlObjectType.SP)
				cmd.CommandType = CommandType.StoredProcedure;

			return cmd;
		}

		/// <summary>
		/// List service for current scope context
		/// </summary>
		protected override IListService<TEntityType> GetListService<TEntityType>(MetaList list)
		{
			return new SqlListService<TEntityType>(this, list.Name, SqlEntityMapper.Current, list);
		}

		/// <summary>
		/// Create entity facade
		/// </summary>
		protected override IEntityFacade<TEntityType> CreateFacade<TEntityType>(string facadeName, IListService<TEntityType> service)
		{
			return new SqlEntityFacade<TEntityType>(facadeName, FacadeMapping.SafeGet(facadeName), service);
		}
	}
}